#include "tcp.h"
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>

#include "common/log.h"


#pragma GCC diagnostic ignored "-Wmissing-field-initializers"

namespace evm { namespace event {

static constexpr int TCP_BACKLOG = 511;  // TCP listen backlog
static constexpr int MAX_ACCEPTS_PER_CALL = 1000;

static void set_nonblock(int fd) {
    int flags;
    if ((flags = fcntl(fd, F_GETFL)) == -1) {
        close(fd);
        throw_with_log("fcntl get: %s", strerror(errno));
    }
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
        close(fd);
        throw_with_log("fcntl set: %s", strerror(errno));
    }
}

TCPServer::TCPServer(const string &host, uint16_t port, EventLoop* loop, TCPHandler handler)
        : host_(host), port_(port), loop_(loop), handler_(handler) {
    if (!loop) {
        loop_ = new EventLoop(1024);
        own_loop_ = true;
    }
}

TCPServer::~TCPServer() {
    if (own_loop_)
        delete loop_;
}

void TCPServer::start() {
    struct addrinfo hints{}, *servinfo;
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    int rv;
    if ((rv = getaddrinfo(host_.data(), std::to_string(port_).data(), &hints, &servinfo)) != 0)
        throw_with_log("getaddrinfo: %s", gai_strerror(rv));

    auto p = servinfo;
    for (; p != NULL; p = p->ai_next) {
        if ((fd_ = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
            continue;

        int reuse = 1;
        if (setsockopt(fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
            throw_with_log("setsockopt: %s", strerror(errno));

        if (bind(fd_, p->ai_addr, p->ai_addrlen) == -1) {
            close(fd_);
            throw_with_log("bind: %s", strerror(errno));
        }

        if (listen(fd_, TCP_BACKLOG) == -1) {
            close(fd_);
            throw_with_log("listen: %s", strerror(errno));
        }
        break;
    }
    if (p ==NULL)
        throw_with_log("create socket failed");

    freeaddrinfo(servinfo);

    set_nonblock(fd_);

    loop_->add_event(fd_, EV_READABLE, [this](int fd, int) {
        for (int accepts = MAX_ACCEPTS_PER_CALL; accepts; --accepts) {

            struct sockaddr_storage sa;
            socklen_t sa_len = sizeof(sa);
            int conn_fd;
            while (true) {
                conn_fd = accept(fd, (struct sockaddr*)(&sa), &sa_len);
                if (conn_fd == -1) {
                    if (errno == EINTR)
                        continue;
                    else {
                        if (errno != EWOULDBLOCK)
                            log_error("accept: {}", strerror(errno));
                        return;  // no more accepts
                    }
                }
                break;
            }

            set_nonblock(conn_fd);

            char conn_ip[INET_ADDRSTRLEN];
            int conn_port;
            struct sockaddr_in *s = (struct sockaddr_in *)&sa;
            inet_ntop(AF_INET, static_cast<const void*>(&s->sin_addr), conn_ip, sizeof(conn_ip));
            conn_port = ntohs(s->sin_port);
            log_info("accept connection: {}:{}", conn_ip, conn_port);

            ++connections;

            if (handler_)
                handler_(conn_fd, loop_);
        }
    });

    loop_->run();

    started_ = true;
    ++connections;
}

void TCPServer::stop() {
    loop_->rmv_event(fd_, EV_READABLE);
    loop_->stop();
    close(fd_);
    fd_ = 0;
    started_ = false;
    --connections;
}

}}
